#include "qrender/point_cloud_renderer.h"

#include <GL/gl.h>

#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>

#include <qrender/camera.h>

PointCloudRenderer::PointCloudRenderer() : Renderer()
{
}

PointCloudRenderer::~PointCloudRenderer()
{
    PointCloudRenderer::release();
}

void PointCloudRenderer::setPoints(const QVector<QVector3D> &points,
                                   QVector3D color, float point_size)
{
    if (points_ == points && color_ == color && point_size_ == point_size) {
        return;
    }

    points_ = points;
    color_ = color;
    point_size_ = point_size;

    data_changed_ = true;
}

void PointCloudRenderer::create()
{
    // 顶点着色器
    const char *vsrc = R"(
#version 330

in vec3 v_pos;

uniform float v_point_size;

uniform mat4 view_matrix;
uniform mat4 projection_matrix;

void main()
{
    gl_Position = projection_matrix * view_matrix * vec4(v_pos, 1.0);
    gl_PointSize = v_point_size;
}
)";
    // 片段着色器
    const char *fsrc = R"(
#version 330 core

uniform vec3 f_color;

out vec4 frag_color;

void main()
{
    frag_color = vec4(f_color, 1.0);
}
)";

    // 创建着色器程序
    program_.reset(new QOpenGLShaderProgram());
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
                                               vsrc); // 顶点着色器
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
                                               fsrc); // 片段着色器
    program_->link();
    program_->bind();

    program_->setUniformValue("view_matrix", QMatrix4x4());
    program_->setUniformValue("projection_matrix", QMatrix4x4());
    program_->setUniformValue("f_color", color_);
    program_->setUniformValue("v_point_size", point_size_);

    vao_.reset(new QOpenGLVertexArrayObject());
    vao_->create();

    // 生成顶点数据
    vbo_.reset(new QOpenGLBuffer());
    vbo_->create();
    vbo_->bind();

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());

        // 告知 OpenGL 如何解析数据, 数据解析格式
        GLuint v_pos = program_->attributeLocation("v_pos");
        program_->setAttributeBuffer(v_pos, GL_FLOAT, 0, 3,
                                     3 * sizeof(GLfloat));
        program_->enableAttributeArray(v_pos);
    }

    vbo_->release();
    program_->release();
}

void PointCloudRenderer::render(const CameraInfo &camera)
{
    // 更新 OpenGL 配置
    auto f = QOpenGLContext::currentContext()->functions();
    f->glEnable(GL_DEPTH_TEST);
    f->glEnable(GL_POLYGON_SMOOTH);
    f->glDisable(GL_CULL_FACE);

    // 启用点精灵
    GLboolean program_point_size, point_sprite;
    f->glGetBooleanv(GL_PROGRAM_POINT_SIZE, &program_point_size);
    f->glGetBooleanv(GL_POINT_SPRITE, &point_sprite);
    f->glEnable(GL_PROGRAM_POINT_SIZE);
    f->glEnable(GL_POINT_SPRITE);

    // 绑定着色器和缓冲区到当前上下文
    program_->bind();
    vbo_->bind();

    // 相机参数
    if (camera.valid) {
        program_->setUniformValue("view_matrix", camera.view_matrix);
        program_->setUniformValue("projection_matrix",
                                  camera.projection_matrix);
    }

    // 模型数据
    program_->setUniformValue("f_color", color_);
    program_->setUniformValue("v_point_size", point_size_);

    {
        vbo_->allocate(points_.constData(), points_.size() * sizeof(QVector3D));
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());
        f->glDrawArrays(GL_POINTS, 0, points_.size());
    }

    vbo_->release();
    program_->release();

    // 还原点精灵
    if (program_point_size) {
        f->glEnable(GL_PROGRAM_POINT_SIZE);
    } else {
        f->glDisable(GL_PROGRAM_POINT_SIZE);
    }

    if (point_sprite) {
        f->glEnable(GL_POINT_SPRITE);
    } else {
        f->glDisable(GL_POINT_SPRITE);
    }
}

void PointCloudRenderer::release()
{
    vao_.reset();
    vbo_.reset();
    program_.reset();
}
